package com.brayan.webapp.controller.security;

import java.io.IOException;
import java.io.Serializable;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedProperty;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.Pattern;

import org.apache.log4j.Logger;

import com.brayan.webapp.model.usermgmt.User;
import com.brayan.webapp.service.security.LoginService;

/**
 * This Login controller must be secured with XML, ejb-jar.xml.
 * http://docs.jboss.org/jbosssecurity/docs/6.0/security_guide/html/
 * J2EE_Declarative_Security_Overview
 * .html#J2EE_Declarative_Security_Overview-EJB_method_permissions
 * 
 * @author Brayan Zimmerli
 * 
 */
@Named
@RequestScoped
public class LoginController implements Serializable {

	private static final long serialVersionUID = 4632605299276716231L;

	private static final String ADMIN = "admin";

	@Inject
	transient private Logger LOGGER;

	@Inject
	@Named("facesContext")
	transient private FacesContext facesContext;

	@Inject
	private LoginService loginService;

	/**
	 * This user holds the user credentials returned from the database AFTER
	 * {@link #login()} has been executed.
	 */
	private User user;

	//@Pattern(regexp = "(^[\\w\\-]([\\.\\w])+[\\w]+@([\\w\\-]+\\.)+[a-zA-Z]{2,4}$)|(^admin$)", message = "Invalid user name.")
	private String username;
	// @Size(min = 0, max = 20, message =
	// "Password must match the length of at least 0-20 characters.")
	private String password;

	private boolean isAdmin = false;
	private boolean isLoggedIn = false;
	private boolean isToRemember = false;

	@ManagedProperty(value = "#{param.requestedURL}")
	private String requestedURL;

	@PostConstruct
	public void init() {
		// If user is in session, populate the user instance.
		Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
		// log.info("Initialise the Login Controller");
		if (sessionMap.get("user") != null) {
			user = (User) facesContext.getExternalContext().getSessionMap().get("user");
			LOGGER.debug("There is logged in users present.");
		}
	}

	public User getUser() {
		if (this.user == null)
			this.user = new User();

		return this.user;
	}

	/**
	 * Store the user in a local field AND in the session map, too.
	 * 
	 * @param User
	 *            The user to hold during the session duration.
	 */
	public void setUser(User user) {
		this.user = user;
		if (user != null)
			facesContext.getExternalContext().getSessionMap().put("user", user);
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password.trim();
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username.trim();
	}

	public boolean isAdmin() {
		User user = (User) facesContext.getExternalContext().getSessionMap().get("user");
		if (user == null)
			this.isAdmin = false;
		else
			this.isAdmin = loginService.isAdmin(user.getUsername());
		;
		return this.isAdmin;
	}

	public boolean isLoggedIn() {
		if (facesContext.getExternalContext().getSessionMap().get("user") != null)
			isLoggedIn = true;
		else
			isLoggedIn = false;
		return isLoggedIn;
	}

	public void setLoggedIn(boolean isLoggedIn) {
		this.isLoggedIn = isLoggedIn;
	}

	public String getRequestedURL() {
		return requestedURL;
	}

	public void setRequestedURL(String requestedURL) {
		this.requestedURL = requestedURL;
	}

	public boolean isToRemember() {
		return isToRemember;
	}

	public void setToRemember(boolean isToRemember) {
		this.isToRemember = isToRemember;
	}

	public void autoLogin() {
		FacesContext facesContext = FacesContext.getCurrentInstance();
		String cookieName = null;
		Cookie cookie[] = ((HttpServletRequest) facesContext.getExternalContext().getRequest()).getCookies();
	}

	/**
	 * The logout conducts 3 tasks:
	 * <ol>
	 * <li>Set the loggedIn flag to false.</li>
	 * <li>Nulls the field-stored user object and creates a newUser.</li>
	 * <li>Invokes the logout method from the request object.</li>
	 * <li>Clears the session.</li>
	 * </ol>
	 */
	public void logout() {
		HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
		String tmpUserName = "";
		try {
			user = (User) facesContext.getExternalContext().getSessionMap().get("user");
			tmpUserName = user.getUsername();
			LOGGER.warn("Attempting to logout user (" + tmpUserName + ").");
			request.logout();
			cleanupUser();
		} catch (ServletException e) {
			LOGGER.warn("Failed to logout with username: " + e.getMessage());
			facesContext.addMessage(null, new FacesMessage("Logout failed.", "Logout exception: " + e.getMessage()));
		}
		setLoggedIn(false);

		// TODO must be translated
		facesContext.addMessage(null, new FacesMessage("Logged out", "Successfully loged out with user name "
				+ tmpUserName + "."));
	}

	private void cleanupUser() {
		// clean up
		// CAUTION! Check if this does not clear the session map of the entire
		// application!?!?
		facesContext.getExternalContext().getSessionMap().clear();
		user = null;
		username = null;
	}

	/**
	 * The login conducts 3 tasks:
	 * <ol>
	 * <li>Invoke the login method of the request object.</li>
	 * <li>Set the user to the field variable.</li>
	 * <li>Do the login check again thru EJB. Check the ejb-jar.xml.</li>
	 * </ol>
	 * 
	 */
	public void login() {
		// check if logged in
		if (isLoggedIn()) {
			// TODO must be translatable
			// String reloginTrans =
			// i18nService.getTrans("PleaseLogoutBeforeLogin", locale);
			facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
					"Please logout before trying to relogin.", null));
			return;
		}
		HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();

		LOGGER.debug("A login request was issued from " + request.getRemoteHost() + ":" + request.getRemotePort());
		// TODO hash the password
		String userName = getUsername();
		String hashedPassword = getPassword();
		try {
			request.login(userName, hashedPassword);
		} catch (ServletException e) {
			LOGGER.info("Failed to login with username " + userName + " and password. " + e.getMessage() + ". ");
			e.printStackTrace();
			facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN, "Login failed",
					"Login failed when trying to authenticate with user " + userName + "."));
			// If this fails, return null.
			return;
		}
		LOGGER.info("Logged in correctly as " + username + ".");

		if (request.isUserInRole("admin"))
			LOGGER.info("The logged in user is an administrator (admin)");
		else
			LOGGER.info("The logged in user is NOT an administrator (admin)");

		// Call business method to check credentials
		getUser().setUsername(getUsername());
		getUser().setPassword(getPassword());
		user = loginService.login(getUser());
		boolean loginOk = false;
		if (user == null) {
			loginOk = false;
		} else {
			loginOk = true;
			// Save the user class in the session map.
			setUser(user);
		}

		setLoggedIn(loginOk);

		// Cookie, navigation and message action
		if (loginOk) {
			// Set the cookie, if needed
			if (isToRemember()) {
				final int COOKIE_DECAY = 1000 * 60 * 60 * 24 * 365 * 1; // 1
																		// years
				Cookie cUser = new Cookie("userId", getUser().getId().toString());
				cUser.setMaxAge(COOKIE_DECAY);
				cUser.setPath(request.getContextPath());
				// Create the key the cookie
				String cCodeString = CookieCodeGenerator.nextSessionId();
				Cookie cCode = new Cookie("cCode", cCodeString);
				cCode.setMaxAge(COOKIE_DECAY);
				cCode.setPath(request.getContextPath());
				HttpServletResponse resp = (HttpServletResponse) facesContext.getExternalContext().getResponse();
				resp.addCookie(cUser);
				resp.addCookie(cCode);
				LOGGER.info("Cookie is set for user " + getUser().getUsername() + " with code " + cCodeString + ".");
			}

			// Set the message.
			facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Successful Login!",
					"Successfully logged in with user " + getUsername() + "."));

			// Check if you could redirect.
			if (requestedURL != null && !requestedURL.isEmpty()) {
				try {
					facesContext.getExternalContext().redirect(requestedURL);
					return;
				} catch (IOException e) {
					LOGGER.error("Failed to forward to original requested URL (" + requestedURL + ")." + e.getMessage());
					facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
							"Failed to redirect after successful login.", e.getMessage()));
				}
			}
			return;
		} else {
			// Must logout, because extended EJB login failed!
			try {
				request.logout();
			} catch (ServletException e) {
				LOGGER.warn("Failed to log out after EJB login failed. " + e.getMessage());
			}
			facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid Login!",
					"No user found with this name (" + getUsername() + ")."));
			// stay on the same page
			return;
		}
	}

	public void login(String email, String password) {
		setUsername(email);
		setPassword(password);
		login();
	}

}
